/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ani.h"
#include <iostream>
#include <cstring>
#include <vector>

#include "runtime/mem/refstorage/reference_storage.h"
#include "libarkbase/utils/logger.h"

typedef enum {
    INVALID_REF_TYPE = 0,
    LOCAL_REF_TYPE = 1,
    GLOBAL_REF_TYPE = 2,
    WEAK_GLOBAL_REF_TYPE = 3
} objectRefType;

static objectRefType GetObjectRefType([[maybe_unused]]ani_env *env, ani_ref obj)
{
    if (UNLIKELY(obj == nullptr)) {
        return INVALID_REF_TYPE;
    }

    switch (ark::mem::ReferenceStorage::GetObjectType(reinterpret_cast<ark::mem::Reference *>(obj))) {
        case ark::mem::Reference::ObjectType::GLOBAL:
            return GLOBAL_REF_TYPE;
        case ark::mem::Reference::ObjectType::WEAK:
            return WEAK_GLOBAL_REF_TYPE;
        case ark::mem::Reference::ObjectType::LOCAL:
        case ark::mem::Reference::ObjectType::STACK:
            return LOCAL_REF_TYPE;
        default:
            UNREACHABLE();
    }
    UNREACHABLE();
}

void checkRefType{{ count }}(ani_env *env,
{% for k in range(count) -%}
    ani_ref ref{{ loop.index }}{% if not loop.last %}, {% endif %}
{% endfor -%})
{
    objectRefType refType;
    {% for k in range(count) -%}
    {
        refType = GetObjectRefType(env, ref{{ loop.index }});
        LOG_IF(refType != LOCAL_REF_TYPE, FATAL, ANI) << "'refType' not equal to 'LOCAL_REF_TYPE'";
    }
    {% endfor -%}
}

void checkNull{{ count }}(ani_env *env,
{% for k in range(count) -%}
    ani_ref ref{{ loop.index }}{% if not loop.last %}, {% endif %}
{% endfor -%})
{
    ani_status status;
    ani_boolean res = ANI_FALSE;
    {% for k in range(count) -%}
    {
        status = env->Reference_IsNull(ref{{ loop.index }}, &res);
        LOG_IF(status != ANI_OK, FATAL, ANI) << "Failed to get null reference";
        LOG_IF(res != ANI_TRUE, FATAL, ANI) << "'res' not equal to ANI_TRUE";
    }
    {% endfor -%}
}

void checkUnderfined{{ count }}(ani_env *env,
{% for k in range(count) -%}
    ani_ref ref{{ loop.index }}{% if not loop.last %}, {% endif %}
{% endfor -%})
{
    ani_status status;
    ani_boolean res = ANI_FALSE;
    {% for k in range(count) -%}
    {
        status = env->Reference_IsUndefined(ref{{ loop.index }}, &res);
        LOG_IF(status != ANI_OK, FATAL, ANI) << "Failed to get undefined reference";
        LOG_IF(res != ANI_TRUE, FATAL, ANI) << "'res' not equal to ANI_TRUE";
    }
    {% endfor -%}
}

const std::vector<ani_native_function> methods = {
    {"checkRefType{{ count }}", nullptr, reinterpret_cast<void *>(checkRefType{{ count }})},
    {"checkQuickRefType{{ count }}", nullptr, reinterpret_cast<void *>(checkRefType{{ count }})},
    {"checkNull{{ count }}", nullptr, reinterpret_cast<void *>(checkNull{{ count }})},
    {"checkQuickNull{{ count }}", nullptr, reinterpret_cast<void *>(checkNull{{ count }})},
    {"checkUnderfined{{ count }}", nullptr, reinterpret_cast<void *>(checkUnderfined{{ count }})},
    {"checkQuickUnderfined{{ count }}", nullptr, reinterpret_cast<void *>(checkUnderfined{{ count }})},
};

ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result)
{
    ani_env *env;
    LOG_IF(vm->GetEnv(ANI_VERSION_1, &env) != ANI_OK, FATAL, ANI) << "Unsupported ANI_VERSION_1";

    static const char *moduleName = "EtsAniTests";

    ani_module module;

    LOG_IF(env->FindModule(moduleName, &module) != ANI_OK, FATAL, ANI) << "Not found '" << moduleName << "'";

    for (auto &m : methods) {
        LOG_IF(env->Module_BindNativeFunctions(module, &m, 1) != ANI_OK, FATAL, ANI) << "Failed to bind function '" << m.name << "'";
    }
    *result = ANI_VERSION_1;
    return ANI_OK;
}
